package org.manon.json.analyse;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.manon.json.ConfigurationException;
import org.manon.json.JSONException;
import org.manon.json.annotation.JSon;
import org.manon.json.annotation.JSonDiscriminator;
import org.manon.json.annotation.JSonDiscriminatorType;
import org.manon.json.annotation.JSonDiscriminatorValue;
import org.manon.json.annotation.JSonSerializer;
import org.manon.json.meta.Entity;
import org.manon.json.meta.MetaProperty;
import org.manon.json.meta.MetaPropertyDiscriminator;
import org.manon.json.meta.MetaPropertyGeneric;
import org.manon.json.util.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author <a href="mailto:jacques.militello@gmail.com">Jacques Militello</a>
 */
public final class ClassRegistry {

	/**
	 * SLF4J Logger
	 */
	private final Logger _logger = LoggerFactory.getLogger(ClassRegistry.class);

	/**
	 * Holder of all entities
	 */
	private final Map<Class<?>,Entity> _entityHolder = new HashMap<Class<?>, Entity>();
	
	private Tree<Entity,String> _tree;
	
	public void register(Class<?> toRegister) {
		
		final JSon json = toRegister.getAnnotation(JSon.class);
		if (json == null) {
			throw new JSONException("This class [" + toRegister.getName() + "] is not annotated with @JSON");
		}

		if (_logger.isInfoEnabled()) {
			_logger.info("Register class [{}]", toRegister);
		}

		analyseClass(json,toRegister);
	}
	
	
	public Tree<Entity,String> createNewTreeAnalyser() {
		return new TreeAnalyser(_entityHolder).buildTree();
	}
	
	private void analyseClass(JSon json,Class<?> toRegister) {

		String entityName = (json.value().length() > 0) ? json.value() : toRegister.getSimpleName();
		
		Entity entity = new Entity(toRegister,entityName,json.filterable());
		
		final JSonSerializer serializer = toRegister.getAnnotation(JSonSerializer.class);
		
		
		if (serializer != null) {
			entity.setCustomSerialiser(serializer.impl());
		} else {
			Set<MetaProperty> properties = ClassAnalyser.analyse(toRegister);
			
			if (properties.isEmpty()) {
				if (_logger.isDebugEnabled()) {
					_logger.debug("No properties for this class [{}]  ...",toRegister);
				}
			} else {
				entity.addProperties(properties);	
			}
		}
		
		_entityHolder.put(entity.getEntityClass(), entity);
	}

	public Collection<Entity> values() {
		return _entityHolder.values();
	}
	
	public Collection<Entity> treeValues() {
		return _tree.values();
	}

	public Set<Class<?>> keySet() {
		return _entityHolder.keySet();
	}


	public Entity get(Class<?> key) {
		return _entityHolder.get(key);
	}

	public void freeze() {
		_tree = createNewTreeAnalyser();
		final Iterator<TreeNode<Entity, String>> iterator =  _tree.iterator();
		TreeNode<Entity, String> node;
		while (iterator.hasNext()) {
			node = iterator.next();
			JSonDiscriminator discriminator = node.getObject().getEntityClass().getAnnotation(JSonDiscriminator.class);
			if (discriminator != null && !node.isLeaf()) {
				add(node,discriminator);
			} else if (discriminator == null) {
				findDiscriminator(node);
			}
			
			
			
			if (node.getObject().hasGeneric()) {
				findGenericType(node);
			}
		}
	}


	private void findDiscriminator(TreeNode<Entity, String> parent) {
		final Iterator<TreeNode<Entity, String>> iterator =  parent.iterator();
		TreeNode<Entity, String> node;
		while (iterator.hasNext()) {
			node = iterator.next();
			JSonDiscriminator discriminator = node.getObject().getEntityClass().getAnnotation(JSonDiscriminator.class);
			if (discriminator != null) {
				add(node,discriminator);
			} else if (!node.isLeaf()){
				findDiscriminator(node);
			}
		}
	}


	private void findGenericType(TreeNode<Entity, String> node) {
		final List<MetaPropertyGeneric> generics = node.getObject().getGenericProperties();
		for (MetaPropertyGeneric generic : generics) {
			if (generic.isInstance()) {
				continue;
			}
			x(node,generic,generic.getGenericType());
			
		}
	}

	@SuppressWarnings("rawtypes")
	private void x(TreeNode<Entity, String> parent,MetaPropertyGeneric generic,String name) {
		
		final Iterator<TreeNode<Entity, String>> iterator =  parent.iterator();
		TreeNode<Entity, String> node;
		while (iterator.hasNext()) {
			node = iterator.next();
			
			ParameterizedType type = (ParameterizedType)node.getObject().getEntityClass().getGenericSuperclass();
			
			if (type.getRawType() instanceof Class) {
				TypeVariable[] vars = ((Class<?>)type.getRawType()).getTypeParameters();
				int i = 0;
				for (;i < vars.length ; i++) {
					if (vars[i].getName().equals(name)) {
						break;
					}
				}
				if (i == vars.length)  {
					throw new RuntimeException();
				}				
				
				if (type.getActualTypeArguments()[i] instanceof TypeVariable) {
					x(node,generic,((TypeVariable)type.getActualTypeArguments()[i]).getName());
				} else {
					node.getObject().getProperties().add(0,generic.clone((Class<?>) type.getActualTypeArguments()[i]));	
				}
				
				
			}
		}
	}

	private void add(TreeNode<Entity, String> node, JSonDiscriminator discriminator) {
		
		final Iterator<TreeNode<Entity, String>> iterator =  node.iterator();
		
		TreeNode<Entity, String> child;
		while (iterator.hasNext()) {
			MetaPropertyDiscriminator property = new MetaPropertyDiscriminator(discriminator.name(), discriminator.name(), String.class);
			property.setType(discriminator.type());
			property.setProperty(discriminator.property());
			child = iterator.next();
			if (discriminator.type() == JSonDiscriminatorType.PROPERTY) {
				try {
					property.setValue(child.getObject().getEntityClass().getAnnotation(JSonDiscriminatorValue.class).value());	
				} catch (Exception cause) {
					throw new ConfigurationException("Failed to configure Entity [" + child.getObject() + "] -> no @JSonDiscriminatorValue", cause);
				}
			} else {
				property.setValue(child.getObject().getId());
			}
			
			child.getObject().getProperties().add(0,property);
			
			if (!child.isLeaf()) {
				add(child,discriminator);
			}
		}
		
	}


	public boolean hasChild(Entity entity) {
		
		if (_tree == null) {
			throw new JSONException("Class Registry is not freeze !");
		}
		
		 TreeNode<Entity,String> node =  _tree.search(ClassUtils.path(entity.getEntityClass()));
		 
		 return (!node.isLeaf());
	}


	public Entity search(String[] path) {
		return _tree.search(path).getObject();
	}

	public TreeNode<Entity, String> searchPath(String[] path) {
		return _tree.search(path);
	}
	
	
}